using QRCoder;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Transforms;
using SixLabors.Primitives;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using Urs.Core;
using Urs.Core.Data;
using Urs.Data.Domain.Stores;
using Urs.Data.Domain.Media;
using Urs.Core.Infrastructure;
using Urs.Data.Domain.Configuration;
using Urs.Services.Configuration;
using Urs.Services.Logging;

namespace Urs.Services.Media
{
    public partial class PictureService : IPictureService
    {
        #region Fields

        private static readonly object s_lock = new object();

        private readonly IRepository<Picture> _pictureRepository;
        private readonly IRepository<GoodsPicture> _goodsPictureRepository;
        private readonly ISettingService _settingService;
        private readonly IWebHelper _webHelper;
        private readonly IUrsFileProvider _fileProvider;
        private readonly ILogger _logger;
        private readonly MediaSettings _mediaSettings;

        #endregion

        #region Ctor

        public PictureService(IRepository<Picture> pictureRepository,
            IRepository<GoodsPicture> goodsPictureRepository,
            ISettingService settingService, IWebHelper webHelper,
            IUrsFileProvider fileProvider,
            ILogger logger, 
            MediaSettings mediaSettings)
        {
            this._pictureRepository = pictureRepository;
            this._goodsPictureRepository = goodsPictureRepository;
            this._settingService = settingService;
            this._webHelper = webHelper;
            this._fileProvider = fileProvider;
            this._logger = logger;
            this._mediaSettings = mediaSettings;
        }

        #endregion

        #region Utilities

        protected virtual string GetFileExtensionFromMimeType(string mimeType)
        {
            if (mimeType == null)
                return "jpg";

            string[] parts = mimeType.Split('/');
            string lastPart = parts[parts.Length - 1];
            switch (lastPart)
            {
                case "pjpeg":
                    lastPart = "jpg";
                    break;
                case "x-png":
                    lastPart = "png";
                    break;
                case "x-icon":
                    lastPart = "ico";
                    break;
                default:
                    lastPart = "jpg";
                    break;
            }
            return lastPart;
        }


        protected virtual void SavePictureInFile(int pictureId, byte[] pictureBinary, string mimeType)
        {
            string lastPart = GetFileExtensionFromMimeType(mimeType);
            string localFilename = string.Format("{0}_0.{1}", pictureId.ToString("0000000"), lastPart);
            var file = Path.Combine(LocalImagePath, localFilename);
            if (File.Exists(file))
                File.WriteAllBytes(file, pictureBinary);
            else
                using (FileStream fs = File.Create(file))
                {
                    fs.Write(pictureBinary, 0, pictureBinary.Length);
                }
        }

        protected virtual void DeletePictureOnFileSystem(Picture picture)
        {
            if (picture == null)
                throw new ArgumentNullException("picture");

            string lastPart = GetFileExtensionFromMimeType(picture.MimeType);
            string localFilename = string.Format("{0}_0.{1}", picture.Id.ToString("0000000"), lastPart);
            string localFilepath = Path.Combine(LocalImagePath, localFilename);
            if (File.Exists(localFilepath))
            {
                File.Delete(localFilepath);
            }
        }

        protected virtual byte[] EncodeImage<T>(Image<T> image, IImageFormat imageFormat, int? quality = null) where T : struct, IPixel<T>
        {
            using (var stream = new MemoryStream())
            {
                var imageEncoder = SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.FindEncoder(imageFormat);
                switch (imageEncoder)
                {
                    case JpegEncoder jpegEncoder:
                        jpegEncoder.IgnoreMetadata = true;
                        jpegEncoder.Quality = quality ?? _mediaSettings.DefaultImageQuality;
                        jpegEncoder.Encode(image, stream);
                        break;

                    case PngEncoder pngEncoder:
                        pngEncoder.PngColorType = PngColorType.RgbWithAlpha;
                        pngEncoder.Encode(image, stream);
                        break;

                    case BmpEncoder bmpEncoder:
                        bmpEncoder.BitsPerPixel = BmpBitsPerPixel.Pixel32;
                        bmpEncoder.Encode(image, stream);
                        break;

                    case GifEncoder gifEncoder:
                        gifEncoder.IgnoreMetadata = true;
                        gifEncoder.Encode(image, stream);
                        break;

                    default:
                        imageEncoder.Encode(image, stream);
                        break;
                }

                return stream.ToArray();
            }
        }
        protected virtual Size CalculateDimensions(Size originalSize, int targetSize,
            ResizeType resizeType = ResizeType.LongestSide, bool ensureSizePositive = true)
        {
            float width, height;

            switch (resizeType)
            {
                case ResizeType.LongestSide:
                    if (originalSize.Height > originalSize.Width)
                    {
                        width = originalSize.Width * (targetSize / (float)originalSize.Height);
                        height = targetSize;
                    }
                    else
                    {
                        width = targetSize;
                        height = originalSize.Height * (targetSize / (float)originalSize.Width);
                    }

                    break;
                case ResizeType.Width:
                    width = targetSize;
                    height = originalSize.Height * (targetSize / (float)originalSize.Width);
                    break;
                case ResizeType.Height:
                    width = originalSize.Width * (targetSize / (float)originalSize.Height);
                    height = targetSize;
                    break;
                default:
                    throw new Exception("Not supported ResizeType");
            }

            if (!ensureSizePositive)
                return new Size((int)Math.Round(width), (int)Math.Round(height));

            if (width < 1)
                width = 1;
            if (height < 1)
                height = 1;

            return new Size((int)Math.Round(width), (int)Math.Round(height));
        }

        protected virtual void DeletePictureThumbs(Picture picture)
        {
            string filter = string.Format("{0}*.*", picture.Id.ToString("0000000"));
            string[] currentFiles = System.IO.Directory.GetFiles(this.LocalThumbImagePath, filter);
            foreach (string currentFileName in currentFiles)
                File.Delete(Path.Combine(this.LocalThumbImagePath, currentFileName));
        }

        protected virtual byte[] ValidatePicture(byte[] pictureBinary, string mimeType)
        {
            using (var image = Image.Load(pictureBinary, out var imageFormat))
            {
                if (Math.Max(image.Height, image.Width) > _mediaSettings.MaximumImageSize)
                {
                    image.Mutate(imageProcess => imageProcess.Resize(new ResizeOptions
                    {
                        Mode = ResizeMode.Max,
                        Size = new Size(_mediaSettings.MaximumImageSize)
                    }));
                }

                return EncodeImage(image, imageFormat);
            }
        }
        protected virtual void SaveThumb(string thumbFilePath, string thumbFileName, string mimeType, byte[] binary)
        {
            var thumbsDirectoryPath = _fileProvider.GetAbsolutePath(@"images\thumbs");
            _fileProvider.CreateDirectory(thumbsDirectoryPath);

            _fileProvider.WriteAllBytes(thumbFilePath, binary);
        }
        protected virtual bool GeneratedThumbExists(string thumbFilePath, string thumbFileName)
        {
            return _fileProvider.FileExists(thumbFilePath);
        }

        protected virtual string GetThumbLocalPath(string thumbFileName)
        {
            var thumbsDirectoryPath = _fileProvider.GetAbsolutePath(@"images\thumbs");

            var thumbFilePath = _fileProvider.Combine(thumbsDirectoryPath, thumbFileName);
            return thumbFilePath;
        }
        protected virtual string GetThumbUrl(string thumbFileName, string storeLocation = null)
        {
            storeLocation = !string.IsNullOrEmpty(storeLocation)
                                    ? storeLocation
                                    : _webHelper.GetStoreLocation();
            var url = storeLocation + "images/thumbs/";

            url = url + thumbFileName;
            return url;
        }

        #endregion

        #region Methods

        public virtual string GetDefaultPictureUrl(int targetSize = 0, PictureType defaultPictureType = PictureType.Entity, bool? useSsl = null)
        {
            string defaultImageName;
            switch (defaultPictureType)
            {
                case PictureType.Entity:
                    defaultImageName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.jpg");
                    break;
                case PictureType.Avatar:
                    defaultImageName = _settingService.GetSettingByKey("Media.User.DefaultAvatarImageName", "default-avatar.jpg");
                    break;
                default:
                    defaultImageName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.gif");
                    break;
            }

            var filePath = GetPictureLocalPath(defaultImageName);
            if (!_fileProvider.FileExists(filePath))
            {
                return string.Empty;
            }
            var storeLocation = useSsl.HasValue
                ? _webHelper.GetStoreLocation(useSsl.Value)
                : _webHelper.GetStoreLocation();
            if (targetSize == 0)
            {
                var url = (!string.IsNullOrEmpty(storeLocation)
                                   ? storeLocation
                                   : _webHelper.GetStoreLocation())
                                   + "images/" + defaultImageName;
                return url;
            }
            else
            {
                var fileExtension = _fileProvider.GetFileExtension(filePath);

                var thumbFileName = $"{_fileProvider.GetFileNameWithoutExtension(filePath)}_{targetSize}{fileExtension}";
                var thumbFilePath = GetThumbLocalPath(thumbFileName);
                if (!GeneratedThumbExists(thumbFilePath, thumbFileName))
                {
                    using (var image = Image.Load(filePath, out var imageFormat))
                    {
                        image.Mutate(imageProcess => imageProcess.Resize(new ResizeOptions
                        {
                            Mode = ResizeMode.Max,
                            Size = CalculateDimensions(image.Size(), targetSize)
                        }));
                        var pictureBinary = EncodeImage(image, imageFormat);
                        SaveThumb(thumbFilePath, thumbFileName, imageFormat.DefaultMimeType, pictureBinary);
                    }
                }

                var url = GetThumbUrl(thumbFileName, storeLocation);
                return url;
            }
        }

        public int DownloadPictureUrl(string imageUrl)
        {
            int pictureId = 0;
            try
            {
                using (HttpClient client = new HttpClient())
                {
                    Byte[] resultBytes = client.GetByteArrayAsync(imageUrl).Result;
                    var picture = InsertPicture(resultBytes, "image/jpeg", string.Empty, false);
                    if (picture != null)
                    {
                        pictureId = picture.Id;
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message, ex);
            }
            return pictureId;
        }

        public virtual byte[] LoadPictureFromFile(int pictureId, string mimeType)
        {
            string lastPart = GetFileExtensionFromMimeType(mimeType);
            string localFilename = string.Format("{0}_0.{1}", pictureId.ToString("0000000"), lastPart);
            if (!File.Exists(Path.Combine(LocalImagePath, localFilename)))
                return new byte[0];
            return File.ReadAllBytes(Path.Combine(LocalImagePath, localFilename));
        }

        public virtual byte[] LoadPictureBinary(Picture picture)
        {
            if (picture == null)
                throw new ArgumentNullException("picture");

            byte[] result = LoadPictureFromFile(picture.Id, picture.MimeType);
            return result;
        }

        public virtual string GetPictureUrl(int pictureId, int targetSize = 0, bool showDefaultPicture = true, bool? useSsl = null,
            PictureType defaultPictureType = PictureType.Entity)
        {
            var picture = GetPictureById(pictureId);
            return GetPictureUrl(picture, targetSize, showDefaultPicture, useSsl, defaultPictureType);
        }

        public virtual string GetPictureUrl(Picture picture, int targetSize = 0, bool showDefaultPicture = true, bool? useSsl = null,
            PictureType defaultPictureType = PictureType.Entity)
        {
            string url = string.Empty;
            byte[] pictureBinary = null;
            if (picture != null)
                pictureBinary = LoadPictureBinary(picture);
            if (picture == null || pictureBinary == null || pictureBinary.Length == 0)
            {
                if (showDefaultPicture)
                {
                    url = GetDefaultPictureUrl(targetSize, defaultPictureType, useSsl: useSsl);
                }
                return url;
            }

            string lastPart = GetFileExtensionFromMimeType(picture.MimeType);
            string thumbFileName;
            string seoFileName = picture.SeoFilename; // = GetPictureSeName(picture.SeoFilename); //just for sure
            if (targetSize == 0)
            {
                thumbFileName = //!String.IsNullOrEmpty(seoFileName) ?
                    string.Format("{0}.{1}", picture.Id.ToString("0000000"), lastPart);
            }
            else
            {
                thumbFileName = //!String.IsNullOrEmpty(seoFileName) ?
                    string.Format("{0}_{1}.{2}", picture.Id.ToString("0000000"), targetSize, lastPart);

            }
            var thumbFilePath = GetThumbLocalPath(thumbFileName);

            using (var mutex = new Mutex(false, thumbFileName))
            {
                if (!GeneratedThumbExists(thumbFilePath, thumbFileName))
                {
                    mutex.WaitOne();

                    if (!GeneratedThumbExists(thumbFilePath, thumbFileName))
                    {
                        byte[] pictureBinaryResized;
                        if (targetSize != 0)
                        {
                            using (var image = Image.Load(pictureBinary, out var imageFormat))
                            {
                                image.Mutate(imageProcess => imageProcess.Resize(new ResizeOptions
                                {
                                    Mode = ResizeMode.Max,
                                    Size = CalculateDimensions(image.Size(), targetSize)
                                }));

                                pictureBinaryResized = EncodeImage(image, imageFormat);
                            }
                        }
                        else
                        {
                            pictureBinaryResized = pictureBinary.ToArray();
                        }

                        SaveThumb(thumbFilePath, thumbFileName, picture.MimeType, pictureBinaryResized);
                    }

                    mutex.ReleaseMutex();
                }
            }

            url = GetThumbUrl(thumbFileName, useSsl.HasValue
                ? _webHelper.GetStoreLocation(useSsl.Value)
                : _webHelper.GetStoreLocation());
            return url;
        }

        public virtual string GetPictureLocalPath(string fileName)
        {
            return _fileProvider.GetAbsolutePath("images", fileName);
        }

        public virtual string GetPictureLocalPath(Picture picture, int targetSize = 0, bool showDefaultPicture = true)
        {
            var url = GetPictureUrl(picture, targetSize, showDefaultPicture);
            if (string.IsNullOrEmpty(url))
                return string.Empty;
            var fileName = _fileProvider.GetFileName(url);
            return _fileProvider.GetAbsolutePath("images", fileName);
        }
        public virtual string GetThumbLocalPath(Picture picture, int targetSize = 0, bool showDefaultPicture = true)
        {
            var url = GetPictureUrl(picture, targetSize, showDefaultPicture);
            if (string.IsNullOrEmpty(url))
                return string.Empty;

            return GetThumbLocalPath(_fileProvider.GetFileName(url));
        }
        public IList<Picture> GetPicturesByIds(int[] pictureIds)
        {
            if (pictureIds == null || pictureIds.Length == 0)
                return new List<Picture>();

            var query = _pictureRepository.Table;

            query = query.Where(x => pictureIds.Contains(x.Id));

            return query.ToList();
        }

        public virtual Picture GetPictureById(int pictureId)
        {
            if (pictureId == 0)
                return null;

            var picture = _pictureRepository.GetById(pictureId);
            return picture;
        }

        public virtual void DeletePicture(Picture picture)
        {
            if (picture == null)
                throw new ArgumentNullException("picture");

            DeletePictureThumbs(picture);

            DeletePictureOnFileSystem(picture);

            _pictureRepository.Delete(picture);
        }

        public virtual IPagedList<Picture> GetPictures(int pageIndex, int pageSize)
        {
            var query = from p in _pictureRepository.Table
                        orderby p.Id descending
                        select p;
            var pics = new PagedList<Picture>(query, pageIndex, pageSize);
            return pics;
        }

        public virtual IList<Picture> GetPicturesByGoodsId(int goodsId)
        {
            return GetPicturesByGoodsId(goodsId, 0);
        }

        public virtual IList<Picture> GetPicturesByGoodsId(int goodsId, int recordsToReturn)
        {
            if (goodsId == 0)
                return new List<Picture>();

            var query = from p in _pictureRepository.Table
                        join pp in _goodsPictureRepository.Table on p.Id equals pp.PictureId
                        orderby pp.DisplayOrder descending
                        where pp.GoodsId == goodsId
                        select p;

            if (recordsToReturn > 0)
                query = query.Take(recordsToReturn);

            var pics = query.ToList();
            return pics;
        }

        public virtual Picture InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename,
            bool isNew, bool validateBinary = true)
        {
            mimeType = CommonHelper.EnsureNotNull(mimeType);
            mimeType = CommonHelper.EnsureMaximumLength(mimeType, 20);

            seoFilename = CommonHelper.EnsureMaximumLength(seoFilename, 100);

            if (validateBinary)
                pictureBinary = ValidatePicture(pictureBinary, mimeType);

            var picture = new Picture()
            {
                MimeType = mimeType,
                SeoFilename = seoFilename
            };
            _pictureRepository.Insert(picture);

            SavePictureInFile(picture.Id, pictureBinary, mimeType);

            return picture;
        }

        public virtual Picture UpdatePicture(int pictureId, byte[] pictureBinary, string mimeType,
            string seoFilename, bool isNew, bool validateBinary = true)
        {
            mimeType = CommonHelper.EnsureNotNull(mimeType);
            mimeType = CommonHelper.EnsureMaximumLength(mimeType, 20);

            seoFilename = CommonHelper.EnsureMaximumLength(seoFilename, 100);

            if (validateBinary)
                pictureBinary = ValidatePicture(pictureBinary, mimeType);

            var picture = GetPictureById(pictureId);
            if (picture == null)
                return null;

            if (seoFilename != picture.SeoFilename)
                DeletePictureThumbs(picture);

            picture.MimeType = mimeType;
            picture.SeoFilename = seoFilename;

            _pictureRepository.Update(picture);

            SavePictureInFile(picture.Id, pictureBinary, mimeType);

            return picture;
        }

        public virtual Picture SetSeoFilename(int pictureId, string seoFilename)
        {
            var picture = GetPictureById(pictureId);
            if (picture == null)
                throw new ArgumentException("No picture found with the specified id");

            if (seoFilename != picture.SeoFilename)
            {
                picture = UpdatePicture(picture.Id, LoadPictureBinary(picture), picture.MimeType, seoFilename, true);
            }
            return picture;
        }


        public void MakeThumbnail(string originalImagePath, string thumbnailPath, int x1, int x2, int y1, int y2)
        {
            System.Drawing.Image originalImage = System.Drawing.Image.FromFile(originalImagePath);

            int towidth = x2 - x1;
            int toheight = y2 - y1;

            int x = 0;
            int y = 0;
            int ow = originalImage.Width;
            int oh = originalImage.Height;

            System.Drawing.Image bitmap = new System.Drawing.Bitmap(towidth, toheight);

            System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap);

            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;

            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

            g.Clear(System.Drawing.Color.Transparent);

            g.DrawImage(originalImage, new System.Drawing.Rectangle(0, 0, towidth, toheight),
                new System.Drawing.Rectangle(x, y, ow, oh),
                System.Drawing.GraphicsUnit.Pixel);

            try
            {
                bitmap.Save(thumbnailPath, System.Drawing.Imaging.ImageFormat.Jpeg);
            }
            catch (System.Exception e)
            {
                throw e;
            }
            finally
            {
                originalImage.Dispose();
                bitmap.Dispose();
                g.Dispose();
            }
        }

        public byte[] GenerateQrcode(string strData, string qrEncoding = "Byte", string level = "H", int version = 8, int scale = 4)
        {
            QRCodeGenerator qrGenerator = new QRCodeGenerator();
            QRCodeData qrCodeData = qrGenerator.CreateQrCode(strData, QRCodeGenerator.ECCLevel.Q);
            QRCode qrCode = new QRCode(qrCodeData);

            var qrCodeImage = qrCode.GetGraphic(5, System.Drawing.Color.Black, System.Drawing.Color.White, null, 15, 6, false);

            MemoryStream ms = null;
            try
            {
                ms = new MemoryStream();
                qrCodeImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                byte[] byteImage = new Byte[ms.Length];
                byteImage = ms.ToArray();
                return byteImage;
            }
            catch (ArgumentNullException ex)
            {
                throw ex;
            }
            finally
            {
                ms.Close();
            }
        }
        public bool ExistsImageQrInFile(string imageName, string mimeType = "")
        {
            string lastPart = GetFileExtensionFromMimeType(mimeType);
            var path = Path.Combine(LocalImageQrPath, string.Format("{0}.{1}", imageName, lastPart));
            return File.Exists(path);
        }
        public void SaveImageQrInFile(string imageName, byte[] pictureBinary, string mimeType = "")
        {
            string lastPart = GetFileExtensionFromMimeType(mimeType);
            var path = Path.Combine(LocalImageQrPath, string.Format("{0}.{1}", imageName, lastPart));

            File.WriteAllBytes(path, pictureBinary);
        }
        public string GetImageQrLocalPath(string imageName, string mimeType = "")
        {
            string lastPart = GetFileExtensionFromMimeType(mimeType);
            var path = Path.Combine(LocalImageQrPath, string.Format("{0}.{1}", imageName, lastPart));
            return path;
        }
        public string GetImageQrUrl(string imageName, string storeLocation = null)
        {
            storeLocation = !string.IsNullOrEmpty(storeLocation)
                                    ? storeLocation
                                    : _webHelper.GetStoreLocation();
            var url = storeLocation + "images/qr/";

            url = url + imageName;
            return url;
        }
        #endregion

        #region Properties

        public long ImageQuality
        {
            get
            {
                return _mediaSettings.DefaultImageQuality;
            }
        }

        protected virtual string LocalThumbImagePath
        {
            get
            {
                string path = _fileProvider.GetAbsolutePath("images/thumbs");
                return path;
            }
        }
        protected virtual string LocalImagePath
        {
            get
            {
                string path = _fileProvider.GetAbsolutePath("images/original");
                return path;
            }
        }
        protected virtual string LocalImageQrPath
        {
            get
            {
                string path = _fileProvider.GetAbsolutePath("images/qr");
                return path;
            }
        }

        #endregion
    }
}
